#include "cmd-user-database.h"

#include "argument.h"
#include "bookmark.h"
#include "comment.h"
#include "console.h"
#include "database.h"
#include "debugger.h"
#include "function.h"
#include "label.h"
#include "loop.h"
#include "stringformat.h"
#include "value.h"
#include "variable.h"

bool cbInstrDbsave(int argc, char* argv[]) {
  DbSave(DbLoadSaveType::All, argc > 1 ? argv[1] : nullptr, argc > 1);
  return true;
}

bool cbInstrDbload(int argc, char* argv[]) {
  if (argc <= 1) {
    DebugRemoveBreakpoints();
    DbClear();
  }
  DbLoad(DbLoadSaveType::All, argc > 1 ? argv[1] : nullptr);
  DebugSetBreakpoints();
  GuiUpdateAllViews();
  return true;
}

bool cbInstrDbclear(int argc, char* argv[]) {
  DebugRemoveBreakpoints();
  DbClear();
  GuiUpdateAllViews();
  return true;
}

bool cbInstrCommentSet(int argc, char* argv[]) {
  if (IsArgumentsLessThan(argc, 3)) return false;
  duint addr = 0;
  if (!valfromstring(argv[1], &addr, false)) return false;
  if (!CommentSet(addr, argv[2], true)) {
    dputs(QT_TRANSLATE_NOOP("DBG", "Error setting comment"));
    return false;
  }
  GuiUpdateAllViews();
  return true;
}

bool cbInstrCommentDel(int argc, char* argv[]) {
  if (IsArgumentsLessThan(argc, 2)) return false;
  duint addr = 0;
  if (!valfromstring(argv[1], &addr, false)) return false;
  if (!CommentDelete(addr)) {
    dputs(QT_TRANSLATE_NOOP("DBG", "Error deleting comment"));
    return false;
  }
  GuiUpdateAllViews();
  return true;
}

bool cbInstrCommentList(int argc, char* argv[]) {
  auto listAuto = argc >= 2 && *argv[1] == '1';
  // setup reference view
  GuiReferenceInitialize(
      GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Comments")));
  GuiReferenceAddColumn(2 * sizeof(duint),
                        GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Address")));
  GuiReferenceAddColumn(
      64, GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Disassembly")));
  GuiReferenceAddColumn(10,
                        GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Comment")));
  GuiReferenceSetRowCount(0);
  GuiReferenceReloadData();
  size_t cbsize;
  CommentEnum(0, &cbsize);
  if (!cbsize) {
    dputs(QT_TRANSLATE_NOOP("DBG", "No comments"));
    return true;
  }
  Memory<COMMENTSINFO*> comments(cbsize, "cbInstrCommentList:comments");
  CommentEnum(comments(), 0);
  int total = 0;
  for (int i = 0; i < (int)(cbsize / sizeof(COMMENTSINFO)); i++) {
    if (!listAuto && !comments()[i].manual) continue;
    GuiReferenceSetRowCount(total + 1);
    char addrText[20] = "";
    sprintf_s(addrText, "%p", comments()[i].addr);
    GuiReferenceSetCellContent(total, 0, addrText);
    char disassembly[GUI_MAX_DISASSEMBLY_SIZE] = "";
    if (GuiGetDisassembly(comments()[i].addr, disassembly))
      GuiReferenceSetCellContent(total, 1, disassembly);
    GuiReferenceSetCellContent(total, 2, comments()[i].text.c_str());
    total++;
  }
  varset("$result", total, false);
  GuiReferenceAddCommand(GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Delete")),
                         "commentdel $0");
  dprintf(QT_TRANSLATE_NOOP("DBG", "%d comment(s) listed in Reference View\n"),
          total);
  GuiReferenceReloadData();
  return true;
}

bool cbInstrCommentClear(int argc, char* argv[]) {
  CommentClear();
  GuiUpdateAllViews();
  dputs(QT_TRANSLATE_NOOP("DBG", "All comments deleted!"));
  return true;
}

bool cbInstrLabelSet(int argc, char* argv[]) {
  if (IsArgumentsLessThan(argc, 3)) return false;
  duint addr = 0;
  if (!valfromstring(argv[1], &addr, false)) return false;
  auto temporaryLabel = argc > 3;
  if (!LabelSet(addr, stringformatinline(argv[2]).c_str(), true,
                temporaryLabel)) {
    dputs(QT_TRANSLATE_NOOP("DBG", "Error setting label"));
    return false;
  }
  GuiUpdateAllViews();
  return true;
}

bool cbInstrLabelDel(int argc, char* argv[]) {
  if (IsArgumentsLessThan(argc, 2)) return false;
  duint addr = 0;
  if (!valfromstring(argv[1], &addr, false)) return false;
  if (!LabelDelete(addr)) {
    dputs(QT_TRANSLATE_NOOP("DBG", "Error deleting label"));
    return false;
  }
  return true;
}

bool cbInstrLabelList(int argc, char* argv[]) {
  // setup reference view
  GuiReferenceInitialize(GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Labels")));
  GuiReferenceAddColumn(2 * sizeof(duint),
                        GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Address")));
  GuiReferenceAddColumn(
      100, GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Disassembly")));
  GuiReferenceAddColumn(0, GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Label")));
  GuiReferenceSetRowCount(0);
  GuiReferenceReloadData();
  std::vector<LABELSINFO> labels;
  LabelGetList(labels);
  if (labels.empty()) {
    dputs(QT_TRANSLATE_NOOP("DBG", "No labels"));
    return true;
  }
  auto count = int(labels.size());
  for (int i = 0; i < count; i++) {
    labels[i].addr += ModBaseFromName(labels[i].mod().c_str());
    GuiReferenceSetRowCount(i + 1);
    char addrText[20] = "";
    sprintf_s(addrText, "%p", labels[i].addr);
    GuiReferenceSetCellContent(i, 0, addrText);
    char disassembly[GUI_MAX_DISASSEMBLY_SIZE] = "";
    if (GuiGetDisassembly(labels[i].addr, disassembly))
      GuiReferenceSetCellContent(i, 1, disassembly);
    GuiReferenceSetCellContent(i, 2, labels[i].text.c_str());
  }
  varset("$result", count, false);
  GuiReferenceAddCommand(GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Delete")),
                         "labeldel $0");
  dprintf(QT_TRANSLATE_NOOP("DBG", "%d label(s) listed in Reference View\n"),
          count);
  GuiReferenceReloadData();
  return true;
}

bool cbInstrLabelClear(int argc, char* argv[]) {
  LabelClear();
  GuiUpdateAllViews();
  dputs(QT_TRANSLATE_NOOP("DBG", "All labels deleted!"));
  return true;
}

bool cbInstrBookmarkSet(int argc, char* argv[]) {
  if (IsArgumentsLessThan(argc, 2)) return false;
  duint addr = 0;
  if (!valfromstring(argv[1], &addr, false)) return false;
  if (!BookmarkSet(addr, true)) {
    dputs(QT_TRANSLATE_NOOP("DBG", "Failed to set bookmark!"));
    return false;
  }
  dputs(QT_TRANSLATE_NOOP("DBG", "Bookmark set!"));
  return true;
}

bool cbInstrBookmarkDel(int argc, char* argv[]) {
  if (IsArgumentsLessThan(argc, 2)) return false;
  duint addr = 0;
  if (!valfromstring(argv[1], &addr, false)) return false;
  if (!BookmarkDelete(addr)) {
    dputs(QT_TRANSLATE_NOOP("DBG", "Failed to delete bookmark!"));
    return false;
  }
  dputs(QT_TRANSLATE_NOOP("DBG", "Bookmark deleted!"));
  return true;
}

bool cbInstrBookmarkList(int argc, char* argv[]) {
  // setup reference view
  GuiReferenceInitialize(
      GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Bookmarks")));
  GuiReferenceAddColumn(2 * sizeof(duint),
                        GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Address")));
  GuiReferenceAddColumn(
      100, GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Disassembly")));
  GuiReferenceAddColumn(
      0, GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Label/Comment")));
  GuiReferenceSetRowCount(0);
  GuiReferenceReloadData();
  size_t cbsize;
  BookmarkEnum(0, &cbsize);
  if (!cbsize) {
    dputs(QT_TRANSLATE_NOOP("DBG", "No bookmarks found"));
    return true;
  }
  Memory<BOOKMARKSINFO*> bookmarks(cbsize, "cbInstrBookmarkList:bookmarks");
  BookmarkEnum(bookmarks(), 0);
  int count = (int)(cbsize / sizeof(BOOKMARKSINFO));
  for (int i = 0; i < count; i++) {
    GuiReferenceSetRowCount(i + 1);
    char addrText[20] = "";
    sprintf_s(addrText, "%p", bookmarks()[i].addr);
    GuiReferenceSetCellContent(i, 0, addrText);
    char disassembly[GUI_MAX_DISASSEMBLY_SIZE] = "";
    if (GuiGetDisassembly(bookmarks()[i].addr, disassembly))
      GuiReferenceSetCellContent(i, 1, disassembly);
    char comment[MAX_COMMENT_SIZE] = "";
    if (CommentGet(bookmarks()[i].addr, comment))
      GuiReferenceSetCellContent(i, 2, comment);
    else {
      char label[MAX_LABEL_SIZE] = "";
      if (LabelGet(bookmarks()[i].addr, label))
        GuiReferenceSetCellContent(i, 2, label);
    }
  }
  varset("$result", count, false);
  GuiReferenceAddCommand(GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Delete")),
                         "bookmarkdel $0");
  dprintf(QT_TRANSLATE_NOOP("DBG", "%d bookmark(s) listed\n"), count);
  GuiReferenceReloadData();
  return true;
}

bool cbInstrBookmarkClear(int argc, char* argv[]) {
  LabelClear();
  GuiUpdateAllViews();
  dputs(QT_TRANSLATE_NOOP("DBG", "All bookmarks deleted!"));
  return true;
}

bool cbInstrFunctionAdd(int argc, char* argv[]) {
  if (IsArgumentsLessThan(argc, 3)) return false;
  duint start = 0;
  duint end = 0;
  if (!valfromstring(argv[1], &start, false) ||
      !valfromstring(argv[2], &end, false))
    return false;
  if (!FunctionAdd(start, end, true)) {
    dputs(QT_TRANSLATE_NOOP("DBG", "Failed to add function"));
    return false;
  }
  dputs(QT_TRANSLATE_NOOP("DBG", "Function added!"));
  GuiUpdateAllViews();
  return true;
}

bool cbInstrFunctionDel(int argc, char* argv[]) {
  if (IsArgumentsLessThan(argc, 2)) return false;
  duint addr = 0;
  if (!valfromstring(argv[1], &addr, false)) return false;
  if (!FunctionDelete(addr)) {
    dputs(QT_TRANSLATE_NOOP("DBG", "Failed to delete function"));
    return false;
  }
  dputs(QT_TRANSLATE_NOOP("DBG", "Function deleted!"));
  GuiUpdateAllViews();
  return true;
}

bool cbInstrFunctionList(int argc, char* argv[]) {
  // setup reference view
  GuiReferenceInitialize(
      GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Functions")));
  GuiReferenceAddColumn(2 * sizeof(duint),
                        GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Start")));
  GuiReferenceAddColumn(2 * sizeof(duint),
                        GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "End")));
  GuiReferenceAddColumn(
      100, GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Disassembly (Start)")));
  GuiReferenceAddColumn(
      0, GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Label/Comment")));
  GuiReferenceSetRowCount(0);
  GuiReferenceReloadData();
  size_t cbsize;
  FunctionEnum(0, &cbsize);
  if (!cbsize) {
    dputs(QT_TRANSLATE_NOOP("DBG", "No functions"));
    return true;
  }
  Memory<FUNCTIONSINFO*> functions(cbsize, "cbInstrFunctionList:functions");
  FunctionEnum(functions(), 0);
  int count = (int)(cbsize / sizeof(FUNCTIONSINFO));
  for (int i = 0; i < count; i++) {
    GuiReferenceSetRowCount(i + 1);
    char addrText[20] = "";
    sprintf_s(addrText, "%p", functions()[i].start);
    GuiReferenceSetCellContent(i, 0, addrText);
    sprintf_s(addrText, "%p", functions()[i].end);
    GuiReferenceSetCellContent(i, 1, addrText);
    char disassembly[GUI_MAX_DISASSEMBLY_SIZE] = "";
    if (GuiGetDisassembly(functions()[i].start, disassembly))
      GuiReferenceSetCellContent(i, 2, disassembly);
    char label[MAX_LABEL_SIZE] = "";
    if (LabelGet(functions()[i].start, label))
      GuiReferenceSetCellContent(i, 3, label);
    else {
      char comment[MAX_COMMENT_SIZE] = "";
      if (CommentGet(functions()[i].start, comment))
        GuiReferenceSetCellContent(i, 3, comment);
    }
  }
  varset("$result", count, false);
  GuiReferenceAddCommand(GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Delete")),
                         "functiondel $0");
  dprintf(QT_TRANSLATE_NOOP("DBG", "%d function(s) listed\n"), count);
  GuiReferenceReloadData();
  return true;
}

bool cbInstrFunctionClear(int argc, char* argv[]) {
  FunctionClear();
  GuiUpdateAllViews();
  dputs(QT_TRANSLATE_NOOP("DBG", "All functions deleted!"));
  return true;
}

bool cbInstrArgumentAdd(int argc, char* argv[]) {
  if (IsArgumentsLessThan(argc, 3)) return false;
  duint start = 0;
  duint end = 0;
  if (!valfromstring(argv[1], &start, false) ||
      !valfromstring(argv[2], &end, false))
    return false;
  if (!ArgumentAdd(start, end, true)) {
    dputs(QT_TRANSLATE_NOOP("DBG", "Failed to add argument"));
    return false;
  }
  dputs(QT_TRANSLATE_NOOP("DBG", "Argument added!"));
  GuiUpdateAllViews();
  return true;
}

bool cbInstrArgumentDel(int argc, char* argv[]) {
  if (IsArgumentsLessThan(argc, 2)) return false;
  duint addr = 0;
  if (!valfromstring(argv[1], &addr, false)) return false;
  if (!ArgumentDelete(addr)) {
    dputs(QT_TRANSLATE_NOOP("DBG", "Failed to delete argument"));
    return false;
  }
  dputs(QT_TRANSLATE_NOOP("DBG", "Argument deleted!"));
  GuiUpdateAllViews();
  return true;
}

bool cbInstrArgumentList(int argc, char* argv[]) {
  // setup reference view
  GuiReferenceInitialize(
      GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Arguments")));
  GuiReferenceAddColumn(2 * sizeof(duint),
                        GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Start")));
  GuiReferenceAddColumn(2 * sizeof(duint),
                        GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "End")));
  GuiReferenceAddColumn(
      100, GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Disassembly (Start)")));
  GuiReferenceAddColumn(
      10, GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Label/Comment")));
  GuiReferenceSetRowCount(0);
  GuiReferenceReloadData();
  size_t cbsize;
  ArgumentEnum(0, &cbsize);
  if (!cbsize) {
    dputs(QT_TRANSLATE_NOOP("DBG", "No arguments"));
    return true;
  }
  Memory<ARGUMENTSINFO*> arguments(cbsize, "cbInstrArgumentList:arguments");
  ArgumentEnum(arguments(), 0);
  int count = (int)(cbsize / sizeof(ARGUMENTSINFO));
  for (int i = 0; i < count; i++) {
    GuiReferenceSetRowCount(i + 1);
    char addrText[20] = "";
    sprintf_s(addrText, "%p", arguments()[i].start);
    GuiReferenceSetCellContent(i, 0, addrText);
    sprintf_s(addrText, "%p", arguments()[i].end);
    GuiReferenceSetCellContent(i, 1, addrText);
    char disassembly[GUI_MAX_DISASSEMBLY_SIZE] = "";
    if (GuiGetDisassembly(arguments()[i].start, disassembly))
      GuiReferenceSetCellContent(i, 2, disassembly);
    char label[MAX_LABEL_SIZE] = "";
    if (LabelGet(arguments()[i].start, label))
      GuiReferenceSetCellContent(i, 3, label);
    else {
      char comment[MAX_COMMENT_SIZE] = "";
      if (CommentGet(arguments()[i].start, comment))
        GuiReferenceSetCellContent(i, 3, comment);
    }
  }
  varset("$result", count, false);
  GuiReferenceAddCommand(GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Delete")),
                         "argumentdel $0");
  dprintf(QT_TRANSLATE_NOOP("DBG", "%d argument(s) listed\n"), count);
  GuiReferenceReloadData();
  return true;
}

bool cbInstrArgumentClear(int argc, char* argv[]) {
  ArgumentClear();
  GuiUpdateAllViews();
  dputs(QT_TRANSLATE_NOOP("DBG", "All arguments deleted!"));
  return true;
}

bool cbInstrLoopAdd(int argc, char* argv[]) {
  if (IsArgumentsLessThan(argc, 3)) return false;
  duint start = 0;
  duint end = 0;
  if (!valfromstring(argv[1], &start, false) ||
      !valfromstring(argv[2], &end, false))
    return false;
  if (!LoopAdd(start, end, true)) {
    dputs(QT_TRANSLATE_NOOP("DBG", "Failed to add loop"));
    return false;
  }
  dputs(QT_TRANSLATE_NOOP("DBG", "Loop added!"));
  GuiUpdateAllViews();
  return true;
}

bool cbInstrLoopDel(int argc, char* argv[]) {
  if (IsArgumentsLessThan(argc, 2)) return false;
  duint addr = 0;
  if (!valfromstring(argv[1], &addr, false)) return false;
  duint depth = 0;
  if (argc >= 3 && !valfromstring(argv[2], &depth, false)) return false;
  if (!LoopDelete(int(depth), addr)) {
    dputs(QT_TRANSLATE_NOOP("DBG", "Failed to delete loop"));
    return false;
  }
  dputs(QT_TRANSLATE_NOOP("DBG", "Loop deleted!"));
  GuiUpdateAllViews();
  return true;
}

bool cbInstrLoopList(int argc, char* argv[]) {
  // setup reference view
  GuiReferenceInitialize(GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Loops")));
  GuiReferenceAddColumn(2 * sizeof(duint),
                        GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Start")));
  GuiReferenceAddColumn(2 * sizeof(duint),
                        GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "End")));
  GuiReferenceAddColumn(2 * sizeof(duint),
                        GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Depth")));
  GuiReferenceAddColumn(2 * sizeof(duint),
                        GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Parent")));
  GuiReferenceAddColumn(
      100, GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Disassembly (Start)")));
  GuiReferenceAddColumn(
      0, GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Label/Comment")));
  GuiReferenceSetRowCount(0);
  GuiReferenceReloadData();
  size_t cbsize;
  LoopEnum(0, &cbsize);
  if (!cbsize) {
    dputs(QT_TRANSLATE_NOOP("DBG", "No loops"));
    return true;
  }
  Memory<LOOPSINFO*> loops(cbsize, "cbInstrLoopList:loops");
  LoopEnum(loops(), 0);
  int count = (int)(cbsize / sizeof(LOOPSINFO));
  for (int i = 0; i < count; i++) {
    GuiReferenceSetRowCount(i + 1);
    char addrText[20] = "";
    sprintf_s(addrText, "%p", loops()[i].start);
    GuiReferenceSetCellContent(i, 0, addrText);
    sprintf_s(addrText, "%p", loops()[i].end);
    GuiReferenceSetCellContent(i, 1, addrText);
    sprintf_s(addrText, "%d", loops()[i].depth);
    GuiReferenceSetCellContent(i, 2, addrText);
    sprintf_s(addrText, "%p", loops()[i].parent);
    GuiReferenceSetCellContent(i, 3, addrText);
    char disassembly[GUI_MAX_DISASSEMBLY_SIZE] = "";
    if (GuiGetDisassembly(loops()[i].start, disassembly))
      GuiReferenceSetCellContent(i, 4, disassembly);
    char label[MAX_LABEL_SIZE] = "";
    if (LabelGet(loops()[i].start, label))
      GuiReferenceSetCellContent(i, 5, label);
    else {
      char comment[MAX_COMMENT_SIZE] = "";
      if (CommentGet(loops()[i].start, comment))
        GuiReferenceSetCellContent(i, 5, comment);
    }
  }
  varset("$result", count, false);
  GuiReferenceAddCommand(GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Delete")),
                         "loopdel $0");
  dprintf(QT_TRANSLATE_NOOP("DBG", "%d loop(s) listed\n"), count);
  GuiReferenceReloadData();
  return true;
}

bool cbInstrLoopClear(int argc, char* argv[]) {
  LoopClear();
  GuiUpdateAllViews();
  dputs(QT_TRANSLATE_NOOP("DBG", "All loops deleted!"));
  return true;
}